﻿using System;
using System.CodeDom.Compiler;
using System.IO;
using System.Reflection;
using JClient;

namespace JScripts.CodeDom.Compiler {
	/// <summary>
	/// Klasa definiująca typ Script.
	/// </summary>
	public class ScriptExecutor {
		/// <summary>
		/// Inicjacja typu Script
		/// </summary>
		public ScriptExecutor( ) {
			this.ConnectionApiHandler = null;
			this.ScriptParms = null;
			this.ScriptResult = new ScriptExetuceInfo( );
		}

		/// <summary>
		/// Zmienna appHandlers, typu JXMPP.XMPP przechowuje uchwyty dla skryptu.
		/// </summary>
		public ConnectionProvider ConnectionApiHandler;
		/// <summary>
		/// Zmienna parms, typu Object[] przechowuje parametry dla metody lub funkcji do wywołania ze skryptu.
		/// </summary>
		public Object[ ] ScriptParms;
		/// <summary>
		/// Zmienna language, typu Languages przechowuje informację o języku skryptu.
		/// </summary>
		public Languages ScriptLanguage;
		/// <summary>
		/// Zmienna SR, typu ScriptResults przechowuje wynik działania skryptu.
		/// </summary>
		public ScriptExetuceInfo ScriptResult;

		/// <summary>
		/// Funkcja analizująca skrypt do wykonania go przez kolejne funkcje.
		/// </summary>
		/// <param name="fileName">Ścieżka do pliku skryptu.</param>
		/// <param name="method">Nazwa metody lub funkcji do wywołania ze skryptu.</param>
		/// <param name="references">Referencje do dołączenia do kompilowanego skryptu.</param>
		/// <returns>Zwraca wynik wykonanego skryptu typu ScriptResults</returns>
		public ScriptExetuceInfo RunScript(string fileName, string method, string[ ] references) {
			string exec = fileName.Split('.')[fileName.Split('.').Length - 1].ToLower( );
			switch (exec) {
				case "cpp":
					ScriptLanguage = Languages.Cpp;
					break;
				case "cs":
					ScriptLanguage = Languages.CSharp;
					break;
				case "js":
					ScriptLanguage = Languages.JScript;
					break;
				case "jss":
					ScriptLanguage = Languages.JSharp;
					break;
				case "py":
					ScriptLanguage = Languages.Python;
					break;
				case "vbs":
				case "vb":
					ScriptLanguage = Languages.VisualBasic;
					break;
				default:
					ScriptResult.ScriptError.Add(new CompilerError( ) {
						IsWarning = true,
						ErrorText = "Nieznany język skryptu!\n" + fileName
					});
					break;
			}
			FileInfo fi = new FileInfo(fileName);
			if (fi.Exists) {
				CodeDomProvider provider = null;

				switch (this.ScriptLanguage) {
					case Languages.Cpp:
						provider = new Microsoft.MCpp.MCppCodeProvider( );
						break;
					case Languages.CSharp:
						provider = new Microsoft.CSharp.CSharpCodeProvider( );
						break;
					case Languages.JScript:
						provider = new Microsoft.JScript.JScriptCodeProvider( );
						break;
					case Languages.JSharp:
						provider = new Microsoft.VJSharp.VJSharpCodeProvider( );
						break;
					case Languages.Python:
						provider = new IronPython.CodeDom.PythonProvider( );
						break;
					case Languages.VisualBasic:
						provider = new Microsoft.VisualBasic.VBCodeProvider( );
						break;
				}

				this.CompileScript(fileName, method, provider, references);
			} else {
				this.ScriptResult.ScriptError.Add(new CompilerError( ) {
					IsWarning = true,
					ErrorText = "Plik nie istnieje!\n" + fileName
				});
			}
			return this.ScriptResult;
		}

		/// <summary>
		/// Metoda kompilująca kod źródłowy skryptów.
		/// </summary>
		/// <param name="fileName">Ścieżka do pliku skryptu.</param>
		/// <param name="method">Nazwa metody lub funkcji do wywołania ze skryptu.</param>
		/// <param name="Provider">Klasa kompilująca skrypt.</param>
		/// <param name="References">Referencje do dołączenia do kompilowanego skryptu.</param>
		protected void CompileScript(string fileName, string method, CodeDomProvider Provider, string[ ] References) {
			FileInfo fi = new FileInfo(fileName);
			if (fi.Exists) {
				//ICodeCompiler csCompiler = Provider.CreateCompiler();

				CompilerParameters csParams = new CompilerParameters( );
				csParams.GenerateInMemory = true;
				csParams.GenerateExecutable = false;
				csParams.CompilerOptions = "/optimize";
				csParams.IncludeDebugInformation = false;
				csParams.ReferencedAssemblies.Add("System.dll");
				csParams.ReferencedAssemblies.Add("System.Windows.Forms.dll");
				csParams.ReferencedAssemblies.Add("System.Drawing.dll");

				if (References != null && References.Length != 0)
					for (int j = 0; j < References.Length; j++)
						if (References[j] != null && References[j].Length != 0 && References[j] != "")
							csParams.ReferencedAssemblies.Add(References[j]);

				this.ScriptResult.CompilerResult = Provider.CompileAssemblyFromFile(csParams, fileName);

				if (this.ScriptResult.CompilerResult.Errors.Count == 0) {
					Assembly wynik = ScriptResult.CompilerResult.CompiledAssembly;
					this.RunMethod(wynik, method);
				}
			}
		}

		/// <summary>
		/// Metoda uruchamiająca metody i funkcje ze skryptu.
		/// </summary>
		/// <param name="compiledAssembly">Parametr zawirający wynik kompilacji skryptu.</param>
		/// <param name="method">Nazwa metody lub funkcji do wywołania ze skryptu.</param>
		protected void RunMethod(Assembly compiledAssembly, string method) {
			Type[ ] mtypes = compiledAssembly.GetTypes( );
			Object[ ] _allParms;
			try {
				_allParms = new Object[ScriptParms.Length + 1];
				_allParms[0] = this.ConnectionApiHandler;
				for (int i = 1; i < _allParms.Length; i++)
					_allParms[i] = this.ScriptParms[i - 1];
			} catch {
				_allParms = new Object[1];
				_allParms[0] = this.ConnectionApiHandler;
			}
			this.ScriptResult.ScriptParms = _allParms;
			foreach (Type t in mtypes) {
				MethodInfo mi = t.GetMethod(method, BindingFlags.Public | BindingFlags.Static);
				if (mi != null) {
					try {
						this.ScriptResult.Return = mi.Invoke(null, _allParms);
						this.ScriptResult.RunFinish = true;
					} catch (Exception e) {
						this.ScriptResult.ScriptError.Add(new CompilerError( ) {
							IsWarning = true,
							ErrorText = e.Message
						});
					}
				}
			}
		}
	}
}